﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using Postnuclear.Collections.Adapters;

namespace Postnuclear.Collections
  {
  /// <summary>
  /// Observable collections.
  /// Notifies observer when its content is changed.
  /// Allows to change the content.
  /// </summary>
  /// <typeparam name="T">The type of the elements in the collection.</typeparam>
  public sealed class ObservableCollection<T>
    : IObservableCollection<T>
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="ObservableCollection{T}"/> class.
    /// </summary>
    /// <param name="values">The initial values.</param>
    public ObservableCollection(IEnumerable<T> values)
      : this(new ObservableLinkedList<T>(values))
      {
      Contract.Requires<ArgumentNullException>(values != null);
      }

    /// <summary>
    /// Initializes a new instance of the <see cref="ObservableCollection{T}"/> class.
    /// </summary>
    /// <param name="values">The initial values.</param>
    public ObservableCollection(params T[] values)
      : this(values as IEnumerable<T>)
      {
      Contract.Requires<ArgumentNullException>(values != null);
      }

    /// <summary>
    /// Initializes a new instance of the <see cref="ObservableCollection{T}"/> class.
    /// </summary>
    /// <param name="list">The internal items storage.</param>
    internal ObservableCollection(ObservableLinkedList<T> list)
      {
      Contract.Requires<ArgumentNullException>(list != null);
      Adapter = new NotifyCollectionChangedAdapter<T>(this);

      List = list;
      List.CollectionCleared += (_, e) => CollectionCleared.Raise(this, e);
      List.ItemAdded += (_, e) => ItemAdded.Raise(this, e);
      List.ItemRemoved += (_, e) => ItemRemoved.Raise(this, e);
      List.PropertyChanged += (_, e) => PropertyChanged.Raise(this, e.PropertyName);
      }

    // Events

    /// <summary>
    /// Occurs when collection is cleared.
    /// List of items that were cleared from collection is passed as arguments value.
    /// </summary>
    public event EventHandler<ValueEventArgs<IEnumerable<T>>> CollectionCleared;

    /// <summary>
    /// Occurs when new item is added to collection.
    /// Added item is passed as arguments value.
    /// </summary>
    public event EventHandler<ObservableItemEventArgs<T>> ItemAdded;

    /// <summary>
    /// Occurs when item is removed from collection.
    /// Removed item is passed as arguments value.
    /// </summary>
    public event EventHandler<ObservableItemEventArgs<T>> ItemRemoved;

    /// <summary>
    /// Occurs when the collection changes.
    /// </summary>
    public event NotifyCollectionChangedEventHandler CollectionChanged
      {
      add
        {
        Adapter.CollectionChanged += value;
        }

      remove
        {
        Adapter.CollectionChanged -= value;
        }
      }

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    // Properties

    /// <summary>
    /// Gets the number of elements actually contained in the <see cref="ObservableCollection{T}"/>.
    /// </summary>
    /// <value>
    /// The number of elements actually contained in the <see cref="ObservableCollection{T}"/>.
    /// </value>
    public int Count
      {
      get
        {
        return List.Count;
        }
      }

    /// <summary>
    /// Gets a value indicating whether the <see cref="ObservableCollection{T}"/> is read-only.
    /// </summary>
    /// <value>
    /// <c>true</c> if the <see cref="ObservableCollection{T}"/> is read-only; otherwise, <c>false</c>.
    /// </value>
    public bool IsReadOnly
      {
      get
        {
        return false;
        }
      }

    // Methods

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.</returns>
    public IEnumerator<T> GetEnumerator()
      {
      return List.GetEnumerator();
      }

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>A <see cref="IEnumerator"/> that can be used to iterate through the collection.</returns>
    IEnumerator IEnumerable.GetEnumerator()
      {
      return GetEnumerator();
      }

    /// <summary>
    /// Adds an item to the <see cref="ObservableCollection{T}"/>.
    /// </summary>
    /// <param name="item">The object to add to the <see cref="ObservableCollection{T}"/>.</param>
    public void Add(T item)
      {
      List.AddLast(item);
      }

    /// <summary>
    /// Removes all items from the <see cref="ObservableCollection{T}"/>.
    /// </summary>
    public void Clear()
      {
      List.Clear();
      }

    /// <summary>
    /// Determines whether the <see cref="ObservableCollection{T}"/> contains a specific value.
    /// </summary>
    /// <param name="item">The object to locate in the <see cref="ObservableCollection{T}"/>.</param>
    /// <returns>
    /// <c>true</c> if item is found in the <see cref="ObservableCollection{T}"/>; otherwise, <c>false</c>.
    /// </returns>
    public bool Contains(T item)
      {
      return List.Contains(item);
      }

    /// <summary>
    /// Copies the elements of the <see cref="ObservableCollection{T}"/> to an <see cref="Array"/>,
    /// starting at a particular <see cref="Array"/> index.
    /// </summary>
    /// <param name="array">
    /// The one-dimensional <see cref="Array"/> that is the destination of the elements
    /// copied from <see cref="ObservableCollection{T}"/>. The <see cref="Array"/> must
    /// have zero-based indexing.
    /// </param>
    /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
    public void CopyTo(T[] array, int arrayIndex)
      {
      List.CopyTo(array, arrayIndex);
      }

    /// <summary>
    /// Removes the first occurrence of a specific object from the <see cref="ObservableCollection{T}"/>.
    /// </summary>
    /// <param name="item">The object to remove from the <see cref="ObservableCollection{T}"/>.</param>
    /// <returns>
    /// <c>true</c> if item was successfully removed from the <see cref="ObservableCollection{T}"/>;otherwise, <c>false</c>.
    /// This method also returns <c>false</c> if item is not found in the original <see cref="ObservableCollection{T}"/>.
    /// </returns>
    public bool Remove(T item)
      {
      return List.Remove(item);
      }

    #endregion

    #region Private

    // Fields

    private readonly INotifyCollectionChanged Adapter;

    private readonly ObservableLinkedList<T> List;

    #endregion
    }
  }